'use strict'

const crypto = require('crypto')

let _randstrList = [
  'a', 'b', 'c', 'd', 'e', 'f', 'g',
  'h', 'i', 'j', 'k', 'l', 'm', 'n',
  'o', 'p', 'q', 'r', 's', 't', 'u',
  'v', 'w', 'x', 'y', 'z',

  '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'
]

function randstring(length = 8) {
  let rstr = ''
  let ind = 0

  for (let i = 0; i < length; i++) {
    ind = parseInt(Math.random() * _randstrList.length)
    rstr += _randstrList[ind]
  }
  return rstr
}

class HashPassword {

  constructor () {
    Object.defineProperty(this, '__hash_map__', {
      enumerable: false,
      configurable: false,
      writable: true,
      value: (salt, passwd) => {
        return `${salt}${passwd}`
      }
    })

    Object.defineProperty(this, 'hashMap', {
      enumerable: true,
      get: () => {
        return this.__hash_map__
      },

      set: (fmap) => {
        if (typeof fmap === 'function') this.__hash_map__ = fmap
      }
    })

    Object.defineProperty(this, '__key__', {
      enumerable: false,
      configurable: false,
      writable: true,
      value: null
    })

    Object.defineProperty(this, 'key', {
      get: () => {
        return this.__key__
      },

      set: (k) => {
        if (typeof k !== 'string') return
        if (k.length < 16) {
          for (let i = 16 - k.length; i >0; i--) {
            k += '1'
          }
        }

        this.__key__ = k.substring(0, 16)
      }
    })

    Object.defineProperty(this, '__salt_length__', {
      enumerable: false,
      configurable: false,
      writable: true,
      value: 10
    })

    Object.defineProperty(this, 'saltLength', {
      enumerable: true,
      get: () => {
        return this.__salt_length__
      },

      set: (k) => {
        if (typeof k !== 'number') return

        if (k > 20) k = 20

        this.__salt_length__ = parseInt(k)
      }
    })

  }

  hashSalt(salt, passwd) {
    let h = crypto.createHash('sm3')

    h.update(this.__hash_map__(salt, passwd))

    return h.digest('base64url')
  }

  _sm4Encrypt (data, key, iv, options = {}) {
    var h = crypto.createCipheriv('sm4-cbc', key, iv, options)
    let hd = h.update(data, 'utf8', 'base64url')
    hd += h.final('base64url')
    return hd
  }

  _sm4Decrypt (data, key, iv, options = {}) {
    var h = crypto.createDecipheriv('sm4-cbc', key, iv, options)
    let hd = h.update(data, 'base64url', 'utf8')
    hd += h.final('utf8')
    return hd
  }

  make (passwd, encrpyt = true) {
    let saltlen = this.__salt_length__
    if (saltlen <= 0) {
      saltlen = parseInt(Math.random() * 6) + 5
    }

    let salt = randstring(saltlen)

    let iv = ''
    let hash_passwd = this.hashSalt(salt, passwd)

    if (this.key && encrpyt) {
      iv = randstring(16)
      let mix_salt = salt + (parseInt(Math.random() * 1000) + 1001)
      salt = this._sm4Encrypt(mix_salt, this.key, iv)
    }

    return (iv ? iv + '.' : '') + salt + '.' + hash_passwd
  }

  verify (passwd, hashpasswd) {
    let arr = hashpasswd.split('.')
    if (arr.length !== 2 && arr.length !== 3) {
      return {
        ok: false,
        errcode: 'ERROR'
      }
    }

    let salt, sign, iv

    if (arr.length === 3) {
      salt = arr[1]
      sign = arr[2]
      iv = arr[0]
    } else {
      salt = arr[0]
      sign = arr[1]
    }

    try {

      let hash_passwd
      if (iv) {
        let org_salt = this._sm4Decrypt(salt, this.key, iv)
        
        hash_passwd = this.hashSalt(org_salt.substring(0, org_salt.length - 4), passwd)
      } else {
        hash_passwd = this.hashSalt(salt, passwd)
      }

      if (hash_passwd !== sign) {
        return {
          ok: false,
          errcode: 'WRONG'
        }
      }

      return {
        ok: true
      }
    } catch (err) {
      return {
        ok: false,
        errcode: 'FAILED'
      }
    }

  }

}

module.exports = HashPassword
